/* 
 * Copyright (C) 2012 Mark Thompson (mark.artifice@gmail.com)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.artificetech.videolibrary;

import java.text.Collator;
import java.util.Calendar;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.Timer;
import java.util.TimerTask;

import com.artificetech.videolibrary.metadata.MediaQuery;
import com.artificetech.videolibrary.metadata.MediaQuery.VideoDataListener;
import com.artificetech.videolibrary.metadata.VideoData;

import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;

public class MainGalleryActivity extends Activity {
	
	private GalleryView gallery;
	
	// TODO Smart sort for titles containing numbers
	
	/** We use this to do locale-aware string comparisons */
	Collator c=Collator.getInstance();
	
	/** Null-aware string compare. */
	private final int safeStringCompare(String a, String b) {
		if (a==null) return (b==null) ? 0 : -1;
		if (b==null) return 1;
		return c.compare(a, b);
	}

	/** A super-comparator that tests a series of comparisons in turn until a definate ordering is established. */
	private class ChainedComparator<T> implements Comparator<T> {
		Comparator<T>[] compareOrders;
		public ChainedComparator(Comparator<T>... _compareOrders) {
			compareOrders=_compareOrders;
		}
		
		public int compare(T lhs, T rhs) {
			for (Comparator<T> order : compareOrders) {
				int compareResult=order.compare(lhs, rhs);
				if (compareResult!=0) return compareResult;
			}
			return 0;
		}
	}
	
	private final Comparator<VideoData> sortByTitle=new Comparator<VideoData>() {
    	public int compare(VideoData lhs, VideoData rhs) {
			return safeStringCompare(lhs.titleSort, rhs.titleSort);
		}
		
		public String toString() { return getString(R.string.sort_by_title); }
    };
    
    private final Comparator<VideoData> sortByYear=new Comparator<VideoData>() {
		public int compare(VideoData lhs, VideoData rhs) {
			return lhs.releaseDate.compareTo(rhs.releaseDate);
		}
    };
    
    @SuppressWarnings("unchecked")
	private final Comparator<VideoData> sortByYearThenTitle=new ChainedComparator<VideoData>(sortByYear, sortByTitle) {
		public String toString() { return getString(R.string.sort_by_release_date); }
    };
    
    private final Comparator<VideoData> sortByGenre=new Comparator<VideoData>() {
    	public int compare(VideoData lhs, VideoData rhs) {
			return safeStringCompare(lhs.genre, rhs.genre);
		}
    };
    
    @SuppressWarnings("unchecked")
	private final Comparator<VideoData> sortByGenreThenTitle=new ChainedComparator<VideoData>(sortByGenre, sortByTitle) {
		public String toString() { return getString(R.string.sort_by_genre); }
    };
    
    private final Comparator<VideoData> sortByDuration=new Comparator<VideoData>() {
    	public int compare(VideoData lhs, VideoData rhs) {
			return lhs.duration-rhs.duration;
		}
		
		public String toString() { return getString(R.string.sort_by_duration); }
    };
    
    @SuppressWarnings("unchecked")
	private final Comparator<VideoData>[] sortOptions=new Comparator[] { sortByTitle, sortByYearThenTitle, sortByGenreThenTitle, sortByDuration };
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // Create a GLSurfaceView, and set it as the content.
        gallery=new GalleryView(this);
        setContentView(gallery);
        
        // Sort by title.
		setSortOrder(sortByTitle);
		
        // Start loading video data.
       	MediaQuery.asyncGetVideos(MainGalleryActivity.this, gallery);
    }
    
	public boolean onCreateOptionsMenu(Menu menu) {
		SubMenu sortBy=menu.addSubMenu(Menu.NONE, 1, Menu.NONE, getString(R.string.menu_sort_by));
		for (int i=0; i<sortOptions.length; ++i) {
			Comparator<VideoData> sortOption=sortOptions[i];
			MenuItem optionItem=sortBy.add(Menu.NONE, 2+i, Menu.NONE, sortOption.toString()); 
			optionItem.setCheckable(true);
		}
		
		return true;
	}
	
	public boolean onPrepareOptionsMenu(Menu menu) {
		SubMenu sortBy=menu.findItem(1).getSubMenu();
		Comparator<VideoData> selectedSortOption=gallery.glView.renderer.carousel.getSortOrder();
		for (int i=0; i<sortOptions.length; ++i) {
			Comparator<VideoData> sortOption=sortOptions[i];
			MenuItem optionItem=sortBy.findItem(2+i); 
			optionItem.setChecked(sortOption==selectedSortOption);
		}
		
		return true;
	}
	
	public boolean onOptionsItemSelected(MenuItem item) {
		int sortOrderIndex=item.getItemId()-2;
		if (sortOrderIndex>=0 && sortOrderIndex<sortOptions.length) { 
			setSortOrder(sortOptions[sortOrderIndex]);
			return true;
		}
		return super.onOptionsItemSelected(item); 
	}

	public void setSortOrder(Comparator<VideoData> sortOrder) {
		gallery.glView.renderer.carousel.setSortOrder(sortOrder);
		if (android.os.Build.VERSION.SDK_INT>=11) innerInvalidateOptionsMenu();
	}
	
	@TargetApi(11)
	public void innerInvalidateOptionsMenu() {
		invalidateOptionsMenu();
	}
	
	@Override
    protected void onPause() {
    	super.onPause();
    	gallery.onPause();
    }
    
    @Override
    protected void onResume() {
    	super.onResume();
    	gallery.onResume();
    }
    
	class GalleryView extends LinearLayout implements VideoDataListener, GalleryListener<VideoData> {
		GalleryGLView glView; // The 3d section of the view, including the carousel
    	LinearLayout infoPane; // The 2d section of the view.
    	Handler handler; // Make sure things get directed to this thread's event queue.
    	
    	private TextView titleYear;
    	private TextView genre;
    	private TextView director;
    	private TextView cast;
    	private TextView description;
    	private TextView count;
    	private ProgressBar scanProgress;
    	
    	private long lastUpdate;
    	private float textAlpha;
    	private int lastCount;
    	private VideoData info;
    	private static final float TEXT_FADE_OUT_SPEED=0.0025f;
    	private static final float TEXT_FADE_IN_SPEED=0.0025f;
    	
		public GalleryView(Context context) {
			super(context);
			handler=new Handler();
			
			setBackgroundColor(0xFF000000);
			
			glView=new GalleryGLView(context);
			ScrollView infoPaneScroll=new ScrollView(context);
			infoPane=new LinearLayout(context);
			RelativeLayout glPane=new RelativeLayout(context);
			
			titleYear=new TextView(context);
			genre=new TextView(context);
			director=new TextView(context);
			cast=new TextView(context);
			description=new TextView(context);
			count=new TextView(context);
			scanProgress=new ProgressBar(context);
			
			// Construct the left and right panes
			
			glPane.setLayoutParams(new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 3)); // Occupy the left 60% of the screen
			LinearLayout.LayoutParams infoPaneLayout=new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 2); // Occupy the right 40% of the screen.
			infoPaneLayout.setMargins(0, 50, 20, 50);
			infoPaneScroll.setLayoutParams(infoPaneLayout); 
			infoPaneLayout=new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
			infoPane.setLayoutParams(infoPaneLayout);
			infoPane.setOrientation(LinearLayout.VERTICAL);
			infoPaneScroll.addView(infoPane);
			
			// Add components to the left pane
			glView.setId(1);
			count.setId(2);
			glView.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
			glPane.addView(glView);
			RelativeLayout.LayoutParams countLayout=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
			countLayout.addRule(RelativeLayout.ALIGN_RIGHT, glView.getId());
			countLayout.addRule(RelativeLayout.ALIGN_BOTTOM, glView.getId());
			countLayout.setMargins(5, 5, 5, 5);
			count.setLayoutParams(countLayout);
			glPane.addView(count);
			RelativeLayout.LayoutParams progressLayout=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
			progressLayout.addRule(RelativeLayout.LEFT_OF, count.getId());
			progressLayout.addRule(RelativeLayout.ALIGN_BOTTOM, glView.getId());
			progressLayout.setMargins(5, 5, 5, 5);
			scanProgress.setLayoutParams(progressLayout);
			glPane.addView(scanProgress);
			
			// Add components to the right pane
			infoPane.addView(titleYear);
			infoPane.addView(genre);
			infoPane.addView(director);
			infoPane.addView(cast);
			infoPane.addView(description);
			
			int boldStyle=Typeface.BOLD;
			int boldItalicStyle=Typeface.BOLD_ITALIC;
			titleYear.setTypeface(Typeface.DEFAULT_BOLD, boldStyle);	
			genre.setTypeface(Typeface.DEFAULT_BOLD, boldItalicStyle);
			director.setTypeface(Typeface.DEFAULT_BOLD, boldStyle);
			cast.setTypeface(Typeface.DEFAULT_BOLD, boldStyle);
			description.setTypeface(Typeface.DEFAULT_BOLD, boldStyle);
			
			for (int i=0; i<infoPane.getChildCount(); ++i) {
				TextView infoChild=(TextView)infoPane.getChildAt(i);
				// Make the background transparent
				infoChild.setBackgroundColor(0x00000000);
				// Make the text transparent
				infoChild.setTextColor(0x00FFFFFF);
				infoChild.setTextSize(20.0f);
				// Leave a space between elements.
				LinearLayout.LayoutParams spacingParams=new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0);
				spacingParams.setMargins(20, 0, 20, 12);
				infoChild.setLayoutParams(spacingParams);
			}
			
			count.setTypeface(Typeface.DEFAULT_BOLD, boldStyle);
			count.setBackgroundColor(0x00000000);
			count.setTextColor(0xFFFFFFFF);
			count.setTextSize(20.0f);
			
			textAlpha=0;
			lastCount=-1;
			info=null;
			
			glView.renderer.carousel.addListener(this);
			addView(glPane);
			addView(infoPaneScroll);
			
			// Call update frequently.
			TimerTask updateTask=new TimerTask() {
				public void run() {
					// This needs to be called on the UI thread. 
					handler.post(new Runnable() {
						public void run() {
							update();									
						}
					});	
				}
			};
			Timer timer=new Timer();
			timer.schedule(updateTask, 0, 40); // ~25 fps
		}

		/** Call this frequently. */
		public void update() {
			long now=SystemClock.uptimeMillis();
			long elapsed=now-lastUpdate;
			if (elapsed>0) update(elapsed);
			lastUpdate=now;
		}
		
		/** This does the actual work of any update. */
		private void update(long elapsed) {
			float prevAlpha=textAlpha;
			VideoData selected=glView.renderer.carousel.getSelected();
			if (selected==info) {
				// Fade text in.
				textAlpha+=elapsed*TEXT_FADE_IN_SPEED;
				if (textAlpha>1.0f) textAlpha=1.0f;
			}
			else {
				// Fade text out.
				textAlpha-=elapsed*TEXT_FADE_OUT_SPEED;
				if (textAlpha<0.0f) {
					textAlpha=0.0f;
					// Also, update contents,
					info=selected;
					updateTextContents();
				}
			}
			if (textAlpha!=prevAlpha) updateTextAlpha();
			
			int c=glView.renderer.carousel.getCount();
			if (c!=lastCount) {
				count.setText(getResources().getQuantityString(R.plurals.videos_count, c, c));
				lastCount=c;
			}
		}
		
		private void updateTextAlpha() {
			int alpha=(int)(textAlpha*255.0f);
			if (alpha<0) alpha=0; if (alpha>255) alpha=255;
			for (int i=0; i<infoPane.getChildCount(); ++i) {
				TextView infoChild=(TextView)infoPane.getChildAt(i);
				infoChild.setTextColor((alpha<<24) | 0xFFFFFF);
			}
		}
		
		private void updateTextContents() {
			infoPane.removeAllViews();
			
			if (info!=null) {
				String title=info.title;
				if (info.releaseDate!=null) {
					Calendar c=new GregorianCalendar();
					c.setTime(info.releaseDate);
					// title+=" ("+c.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())+")";
					title+=" ("+c.get(Calendar.YEAR)+")";
				}
				infoPane.addView(titleYear);
				titleYear.setText(title);
				
				if (info.genre!=null) {
					genre.setText(info.genre);
					infoPane.addView(genre);
				}
				
				if (info.director!=null) {
					director.setText(getString(R.string.director)+info.director);
					infoPane.addView(director);
				}
				
				if (info.cast!=null) {
					cast.setText(getString(R.string.cast)+info.cast);
					infoPane.addView(cast);
				}
				
				if (info.description!=null) {
					description.setText(info.description);
					infoPane.addView(description);
				}
				
				updateTextAlpha();
			}
		}
		
		public void onPause() {
			glView.onPause();
		}
		
		public void onResume() {
			glView.onResume();
		}
		
		public void videoAdded(VideoData v) {
			glView.renderer.carousel.add(v);
		}

		public void setProgress(float progress) {
			// Does nothing.
		}

		public void scanComplete() {
			// This needs to be called on the UI thread. 
			handler.post(new Runnable() {
				public void run() {
					scanProgress.setVisibility(GONE);
				}
			});	
		}

		public void selectionChanged(CarouselRenderer<VideoData> source, final VideoData selected) {
			// For the moment, we poll for this instead.
//			// This might be called on a separate thread.
//			handler.post(new Runnable() {
//				public void run() {
//					if (selected!=null) textView.setText("Title: "+selected.title);
//					else textView.setText("");		
//				}
//			});
		}

		public void lengthChanged(CarouselRenderer<VideoData> source, int newLength) {
			// For the moment, we poll for this instead.
		}

		public void activateSelection(CarouselRenderer<VideoData> source, VideoData activate) {
			Intent mediaIntent = new Intent(Intent.ACTION_VIEW);
			mediaIntent.setDataAndType(activate.uri, activate.mimeType);
			startActivity(mediaIntent);
		}
    	
    }
    
    class GalleryGLView extends GLSurfaceView {
    	private final MainRenderer renderer;
    	
    	GalleryGLView(Context context) {
    		super(context);
    		
    		// Ask for GL ES 2.0
    		setEGLContextClientVersion(2);
    		
    		renderer=new MainRenderer(context);
    		setRenderer(renderer);    		
    	}
        
        @Override
        public boolean onTouchEvent(MotionEvent e) {
        	return renderer.onTouchEvent(e);
        }
    }
}
